#pragma once

#include "config.h"
#include "mutex.h"

template < typename T, typename Sequence = std::deque<T> >
class ThreadSafeBlockQueue
{
public:
	bool IsEmpty() const	{ return _c.empty(); }
	size_t Size() const		{ return _c.size(); }

	ThreadSafeBlockQueue()
	{
		int rc;
		if ( (rc = pthread_cond_init(&_is_empty, NULL)) != 0)
		{
			fprintf(stderr, "pthread_cond_init() failed: %d\n", rc);
			abort();
		}
	}

	~ThreadSafeBlockQueue()
	{
		/*int rc;
		if ( (rc = pthread_cond_destroy(&_is_empty)) != 0)
		{
		fprintf(stderr, "pthread_cond_destroy() failed: %d\n", rc);
		abort();
		}*/
		pthread_cond_destroy(&_is_empty);
	}	

	void Enqueue(const T &x)
	{
		_mutex.Lock();
		_c.push_back(x);
		int rc;
		if ( (rc = pthread_cond_signal(&_is_empty)) != 0)
		{
			fprintf(stderr, "pthread_cond_signal() failed: %d\n", rc);
			abort();
		}
		_mutex.Unlock();
	}

	void Dequeue(T &x)
	{
		_mutex.Lock();
		while (_c.empty())
		{
			int rc;
			if ( (rc = pthread_cond_wait(&_is_empty, _mutex.Get())) != 0)
			{
				fprintf(stderr, "pthread_cond_wait() failed: %d\n", rc);
				abort();
			}
		}
		x = _c.front();
		_c.pop_front();
		_mutex.Unlock();
	}

	int Dequeue(T &x, long timeout_sec)
	{
		_mutex.Lock();
		while (_c.empty())
		{
			//struct timeval now;
			//gettimeofday(&now, NULL);

			struct timespec ts;
			//ts.tv_nsec = now.tv_usec * 1000;
			//ts.tv_sec = now.tv_sec + timeout_sec;

			ts.tv_sec = (long)time(NULL) + timeout_sec;
			ts.tv_nsec = 0;

			int rc;
			if ( (rc = pthread_cond_timedwait(&_is_empty, _mutex.Get(), &ts)) != 0)
			{
				if (rc == ETIMEDOUT)
				{
					_mutex.Unlock();
					return 1; // time out
				}
				else
				{
					fprintf(stderr, "pthread_cond_wait() failed: %d\n", rc);
					abort();
				}
			}
		}
		x = _c.front();
		_c.pop_front();
		_mutex.Unlock();
		return 0;
	}

	int pop()
	{
		_mutex.Lock();
		_c.pop_front();
		_mutex.Unlock();
		return 0;
	}

	int Front(T &x ,long timeout_sec)
	{
		_mutex.Lock();
		while (_c.empty())
		{
			//struct timeval now;
			//gettimeofday(&now, NULL);

			struct timespec ts;
			//ts.tv_nsec = now.tv_usec * 1000;
			//ts.tv_sec = now.tv_sec + timeout_sec;

			ts.tv_sec = (long)time(NULL) + timeout_sec;
			ts.tv_nsec = 0;

			int rc;
			if ( (rc = pthread_cond_timedwait(&_is_empty, _mutex.Get(), &ts)) != 0)
			{
				if (rc == ETIMEDOUT)
				{
					_mutex.Unlock();
					return 1; // time out
				}
				else
				{
					fprintf(stderr, "pthread_cond_wait() failed: %d\n", rc);
					abort();
				}
			}
		}
		x = _c.front();
		_mutex.Unlock();
		return 0;
	} 

	int WaitItem(long timeout_sec)
	{
		_mutex.Lock();
		while (_c.empty())
		{
			struct timespec ts;
			ts.tv_sec = (long)time(NULL) + timeout_sec;
			ts.tv_nsec = 0;
			int rc;
			if ( (rc = pthread_cond_timedwait(&_is_empty, _mutex.Get(), &ts)) != 0)
			{
				if (rc == ETIMEDOUT)
				{
					_mutex.Unlock();
					return 1;
				}
				else
				{
					fprintf(stderr, "pthread_cond_wait() failed:%d\n", rc);
					abort();
				}
			}
		}
		_mutex.Unlock();
		return 0;
	}


private:
	Sequence _c;
	Mutex _mutex;
	pthread_cond_t _is_empty;
};
